package Lexer;

import java.util.*;

import CompilerSPy.*;

@SuppressWarnings({ "rawtypes", "unchecked" })
public class Lexer {

	public Lexer(char[] input) {
		this.input = input;
		// add an end-of-file label to make it easy to do the lexer
		input[input.length - 1] = '\0';
		// number of the current line
		lineNumber = 1;
		tokenPos = 0;
		lastTokenPos = 0;
		beforeLastTokenPos = 0;
		error = new CompilerError(null);
	}

	private static final int MaxValueInteger = 32767;
	private static final int MinValueInteger = -32768;
	// contains the keywords
	static private Hashtable keywordsTable;

	// this code will be executed only once for each program execution
	static {
		keywordsTable = new Hashtable();

		keywordsTable.put("print", new Integer(Symbol.PRINT));
		keywordsTable.put("break", new Integer(Symbol.BREAK));
		keywordsTable.put("continue", new Integer(Symbol.CONTINUE));
		keywordsTable.put("return", new Integer(Symbol.RETURN));
		keywordsTable.put("if", new Integer(Symbol.IF));
		keywordsTable.put("elif", new Integer(Symbol.ELIF));
		keywordsTable.put("else", new Integer(Symbol.ELSE));
		keywordsTable.put("while", new Integer(Symbol.WHILE));
		keywordsTable.put("for", new Integer(Symbol.FOR));
		keywordsTable.put("range", new Integer(Symbol.RANGE));
		keywordsTable.put("def", new Integer(Symbol.DEF));
		keywordsTable.put("class", new Integer(Symbol.CLASS));
		keywordsTable.put("self", new Integer(Symbol.SELF));
		keywordsTable.put("eof", new Integer(Symbol.EOF));
		keywordsTable.put("id", new Integer(Symbol.ID));
		keywordsTable.put("num", new Integer(Symbol.NUM));
		keywordsTable.put("in", new Integer(Symbol.IN));
		keywordsTable.put("not", new Integer(Symbol.NOT));
		keywordsTable.put("is", new Integer(Symbol.IS));
		// keywordsTable.put("\\n", new Integer(Symbol.NEWLINE));
		keywordsTable.put("asterisk", new Integer(Symbol.ASTERISK));
		// n�o sei se fica assim
		keywordsTable.put("string", new Integer(Symbol.STRING));
		keywordsTable.put("name", new Integer(Symbol.NAME));
		keywordsTable.put("number", new Integer(Symbol.NUMBER));
		// tamb�m n�o sei se tem isso
		keywordsTable.put("lastsymbol", new Integer(Symbol.LastSymbol));
		// o resto foi retirado, pois nunca ser� usado, recomenda��o da
		// Tiemi
	}

	public boolean skipComment() {
		if (input[tokenPos] == '#') {
			while (input[tokenPos] != '\n' && tokenPos < (input.length - 1)) {
				tokenPos++;
			}
			nextToken();
			return true;
		}
		return false;
	}

	public boolean fourSpaces() {
		if (input[tokenPos] == ' ' && input[tokenPos + 1] == ' '
				&& input[tokenPos + 2] == ' ' && input[tokenPos + 3] == ' ') {
			tokenPos += 4;
			return true;
		}
		return false;
	}

	public void nextToken() {
		lastTokenPos = tokenPos;

		if (skipComment())
			return;

		if (indent > 0) {
			token = Symbol.INDENT;
			indent = 0;
			return;
		}
		if (dedent > 0) {
			token = Symbol.DEDENT;
			dedent--;
			return;
		}

		int currentTab = lastTab;

		while (input[tokenPos] == ' ' || input[tokenPos] == '\r'
				|| input[tokenPos] == '\t' || input[tokenPos] == '\n') {

			if (input[tokenPos] == '\n') {
				currentTab = 0;
				if (flag) {
					token = Symbol.NEWLINE;
					flag = false;
					lineNumber++;
					return;
				}
			}

			if ((input[tokenPos] == '\t' || fourSpaces()) && flag == false) {
				currentTab++;
			}
			tokenPos++;
		}

		if (skipComment())
			return;

		flag = true;

		if (currentTab > lastTab) {
			indent = currentTab - lastTab;
			nextToken();
			lastTab = currentTab;
			return;
		} else {
			if (currentTab < lastTab) {
				dedent = lastTab - currentTab;
				nextToken();
				lastTab = currentTab;
				return;
			}
		}
		lastTab = currentTab;

		if (input[tokenPos] == '\0') {
			token = Symbol.EOF;
			System.out.println(token);
			return;
		} else {

			if (!skipComment()) {
				if (Character.isLetter(input[tokenPos])) {
					StringBuffer identifier = new StringBuffer();
					while (Character.isLetterOrDigit(input[tokenPos])) {
						identifier.append(input[tokenPos]);
						tokenPos++;
					}
					Object value = keywordsTable.get(identifier.toString());
					if (value != null)
						// Convert value to integer
						token = ((Integer) value).intValue();
					else {
						token = Symbol.NAME;
						stringValue = identifier.toString();
					}
				} else if (Character.isDigit(input[tokenPos])) {
					while (Character.isDigit(input[tokenPos])) {
						input[tokenPos] = (char) numberValue;
						tokenPos++;
					}
					token = Symbol.NUM;
					System.out.println(token);
					if (numberValue > MaxValueInteger
							|| numberValue < MinValueInteger)
						error.show("Limit exceeded");

				} else {
					switch (input[tokenPos]) {
					case '+':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.PLUSASSIGN;
							System.out.println(token);
							tokenPos++;
						} else {
							token = Symbol.PLUS;
							System.out.println(token);
						}
						break;
					case '-':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.MINUSASSIGN;
							System.out.println(token);
							tokenPos++;
						} else {
							token = Symbol.MINUS;
							System.out.println(token);
						}
						break;
					case '*':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.MULTIASSIGN;
							System.out.println(token);
							tokenPos++;
						} else {
							token = Symbol.MULT;
							System.out.println(token);
						}
						break;
					case '/':
						if (input[tokenPos + 1] == '/') {
							tokenPos++;
							token = Symbol.FLOORDIV;
							System.out.println(token);
						} else {
							if (input[tokenPos + 1] == '=') {
								token = Symbol.DIVASSIGN;
								System.out.println(token);
								tokenPos++;
							} else {
								token = Symbol.DIV;
								System.out.println(token);
							}
						}
						break;
					case ';':
						token = Symbol.SEMICOLON;
						System.out.println(token);
						break;
					case ':':
						token = Symbol.COLON;
						System.out.println(token);
						break;
					case ',':
						token = Symbol.COMMA;
						System.out.println(token);
						break;
					case '~':
						token = Symbol.INVERTION;
						System.out.println(token);
						break;
					case '[':
						token = Symbol.LEFTCURBRACKET;
						System.out.println(token);
						break;
					case ']':
						token = Symbol.RIGHTCURBRACKET;
						System.out.println(token);
						break;
					case '<':
						if (input[tokenPos + 1] == '=') {
							tokenPos++;
							token = Symbol.LE;
							System.out.println(token);
						} else {
							if (input[tokenPos + 1] == '>') {
								tokenPos++;
								token = Symbol.NEQ;
								System.out.println(token);
							} else {
								token = Symbol.LT;
								System.out.println(token);
							}
						}
						break;
					case '>':
						if (input[tokenPos + 1] == '=') {
							tokenPos++;
							token = Symbol.GE;
							System.out.println(token);
						} else {
							token = Symbol.GT;
							System.out.println(token);
						}
						break;
					case '=':
						if (input[tokenPos + 1] == '=') {
							tokenPos++;
							token = Symbol.EQ;
							System.out.println(token);
						} else {
							token = Symbol.ASSIGN;
							System.out.println(token);
							tokenPos++;
						}
						break;
					case '!':
						if (input[tokenPos + 1] == '=') {
							tokenPos++;
							token = Symbol.NEQC;
							System.out.println(token);
						}
						break;
					case '|':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.ORASSIGN;
							tokenPos++;
							System.out.println(token);
						} else {
							token = Symbol.OR;
						}
						break;
					case '^':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.XORASSIGN;
							tokenPos++;
							System.out.println(token);
						} else {
							token = Symbol.XOR;
						}
						break;
					case '&':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.ANDASSIGN;
							tokenPos++;
							System.out.println(token);
						} else {
							token = Symbol.AND;
						}
						break;
					case '%':
						if (input[tokenPos + 1] == '=') {
							token = Symbol.MODASSIGN;
							tokenPos++;
							System.out.println(token);
						} else {
							token = Symbol.MOD;
						}
						break;
					case '(':
						token = Symbol.LEFTPAR;
						System.out.println(token);
						break;
					case ')':
						token = Symbol.RIGHTPAR;
						System.out.println(token);
						break;
					default:
						error.show("Invalid Character: '" + input[tokenPos]
								+ "'", false);
					}
				}
			}
		}

		beforeLastTokenPos = lastTokenPos;
	}

	// return the line number of the last token got with getToken()
	public int getLineNumber() {
		return lineNumber;
	}

	public int getLineNumberBeforeLastToken() {
		return getLineNumber(lastTokenPos);
	}

	private int getLineNumber(int index) {
		// return the line number in which the character input[index] is
		int i, n, size;
		n = 1;
		i = 0;
		size = input.length;
		while (i < size && i < index) {
			if (input[i] == '\n')
				n++;
			i++;
		}
		return n;
	}

	public String getCurrentLine() {
		// return getLine(lastTokenPos);
		return getLine(tokenPos);
	}

	public String getLineBeforeLastToken() {
		return getLine(beforeLastTokenPos);
	}

	private String getLine(int index) {
		// get the line that contains input[index]. Assume input[index] is at a
		// token, not
		// a white space or newline

		int i;
		if (input.length <= 1)
			return "";
		i = index;
		if (i <= 0)
			i = 1;
		else if (i >= input.length)
			i = input.length;

		StringBuffer line = new StringBuffer();
		// go to the beginning of the line
		while (i >= 1 && input[i] != '\n')
			i--;
		if (input[i] == '\n')
			i++;
		// go to the end of the line putting it in variable line
		while (input[i] != '\0' && input[i] != '\n' && input[i] != '\r') {
			line.append(input[i]);
			i++;
		}
		return line.toString();
	}

	public String getStringValue() {
		return stringValue;
	}

	public int getNumberValue() {
		return numberValue;
	}

	public String getLiteralStringValue() {
		return literalStringValue;
	}

	// current token
	public int token;
	private String stringValue, literalStringValue;
	private int numberValue;

	private int lastTab, indent, dedent;

	private int tokenPos;
	// input[lastTokenPos] is the last character of the last token found
	private int lastTokenPos;
	// input[beforeLastTokenPos] is the last character of the token before the
	// last
	// token found
	private int beforeLastTokenPos;
	// program given as input - source code

	private char[] input;

	// number of current line. Starts with 1
	private int lineNumber;

	private CompilerError error;

	boolean flag;

	// private Stack<Integer> stack;
	// not used -> vamos pensar nisso!
}
